
package com.borqs.music;

import java.io.File;

import android.app.Application;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.database.ContentObserver;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.provider.MediaStore.Audio;

import com.borqs.music.database.AudioTraceInfo;
import com.borqs.music.database.DBController;
import com.borqs.music.database.MusicDatabaseHelper;
import com.borqs.music.ui.SignupUploadActivity;
import com.borqs.music.util.MD5Utils;
import com.borqs.music.util.MusicConf;
import com.borqs.music.util.MusicUtils;
import com.borqs.music.util.MyLogger;

public class MusicApplication extends Application {
    public static final String MD5_NULL = "0";
    private static final MyLogger logger = MyLogger.getLogger("MusicApplication");

    private Handler mSyncHandler;
    private MediaObserver mObserver;
    private long mLastUploadTime;
    private DBController mDBController;

    @Override
    public void onCreate() {
        super.onCreate();
        if (MusicUtils.isAccessServerAvailable(this)) {
            MusicConf.updateServerURL(getApplicationContext());
            HandlerThread syncAudioInfoThread = new HandlerThread("SyncAudioInfoHandler Thread");
            syncAudioInfoThread.start();
            mSyncHandler = new AudioSyncHandler(syncAudioInfoThread.getLooper());
            mSyncHandler.sendEmptyMessageDelayed(MSG_SYNC_AUDIO_TRACE_TABLE, 3000);

            mObserver = new MediaObserver(mSyncHandler);
            getContentResolver().registerContentObserver(Audio.Media.EXTERNAL_CONTENT_URI, true,
                    mObserver);

            mDBController = DBController.getInstance(this);

            IntentFilter networkStateFilter = new IntentFilter();
            networkStateFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
            registerReceiver(mNetworkStateReceiver, networkStateFilter);
        }
    }

    @Override
    public void onTerminate() {
        if (MusicUtils.isAccessServerAvailable(this)) {
            getContentResolver().unregisterContentObserver(mObserver);
            unregisterReceiver(mNetworkStateReceiver);
        }
        super.onTerminate();
    }

    private static final int MSG_SYNC_AUDIO_TRACE_TABLE = 1;
    private static final int MSG_CHECK_UPLOAD_SONGS = 2;
    private static final int MSG_CHECK_UPLOAD_LYRICS = 3;
    private static final int MSG_CHECK_UPLOAD_STATISTICS = 4;

    private class AudioSyncHandler extends Handler {
        public AudioSyncHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_SYNC_AUDIO_TRACE_TABLE:
                    syncAudioTraceWithMediaScanner();
                    break;
                case MSG_CHECK_UPLOAD_SONGS:
                    notifyUploadSongs();
                    break;
                case MSG_CHECK_UPLOAD_LYRICS:
                    notifyUploadLyrics();
                    break;
                case MSG_CHECK_UPLOAD_STATISTICS:
                    notifyUploadStatistics();
                    break;
            }
        }
    };

    private synchronized void syncAudioTraceWithMediaScanner() {
        logger.d("syncAudioTraceWithMediaScanner() start");
        if (!MusicUtils.isSdcardUsable()) {
            logger.d("SDCard is not available, stop sync.");
            return;
        }
        long maxID = mDBController.deleteInvalidRecords();
        insertNewAddedRecords(maxID);
        notifyAllUploads();
        logger.d("syncAudioTraceWithMediaScanner() end, maxID:" + maxID);
    }

    private void notifyUploadSongs() {
        if (SignupUploadActivity.isAbleToUpload(this)) {
            Intent intent = new Intent(TransferService.ACTION_START_UPLOAD_SONG);
            intent.setClass(this, TransferService.class);
            startService(intent);
        }
    }

    private void notifyUploadLyrics() {
        if (SignupUploadActivity.isAbleToUpload(this)) {
            Intent intent = new Intent(TransferService.ACTION_START_UPLOAD_LYRIC);
            intent.setClass(this, TransferService.class);
            startService(intent);
        }
    }

    private void notifyUploadStatistics() {
        Intent intent = new Intent(TransferService.ACTION_START_UPLOAD_STATISTICS);
        intent.setClass(this, TransferService.class);
        startService(intent);
    }

    private void insertNewAddedRecords(long maxID) {
        Uri uri = Audio.Media.EXTERNAL_CONTENT_URI;
        final String[] projection = {
                Audio.Media._ID,
                Audio.Media.DATE_ADDED,
                Audio.Media.SIZE,
                Audio.Media.DATA,
        };
        String mediaSortOrder = Audio.Media._ID + " ASC";
        ContentResolver res = getContentResolver();
        String selection = Audio.Media._ID + " > ? ";
        String[] selectionArgs = {
                "" + maxID
        };
        Cursor mediaCursor = res.query(uri, projection, selection, selectionArgs, mediaSortOrder);

        if (mediaCursor == null || mediaCursor.getCount() == 0) {
            if (mediaCursor != null) {
                mediaCursor.close();
            }
            return;
        }

        MusicDatabaseHelper helper = new MusicDatabaseHelper(this);
        SQLiteDatabase db = helper.getWritableDatabase();
        db.beginTransaction();
        try {
            while (mediaCursor.moveToNext()) {
                ContentValues values = new ContentValues();
                values.clear();
                String path = mediaCursor.getString(mediaCursor.getColumnIndex(Audio.Media.DATA));
                values.put(AudioTraceInfo.COLUMN_SONG_ID,
                        mediaCursor.getLong(mediaCursor.getColumnIndex(Audio.Media._ID)));
                values.put(AudioTraceInfo.COLUMN_DATE_ADDED,
                        mediaCursor.getLong(mediaCursor.getColumnIndex(Audio.Media.DATE_ADDED)));
                values.put(AudioTraceInfo.COLUMN_SIZE,
                        mediaCursor.getLong(mediaCursor.getColumnIndex(Audio.Media.SIZE)));
                values.put(AudioTraceInfo.COLUMN_DATA, path);
                values.put(AudioTraceInfo.COLUMN_IS_FAVORITE, 0);
                values.put(AudioTraceInfo.COLUMN_IS_UPLOADED_LYRIC,
                        AudioTraceInfo.VALUE_OF_NOT_UPLOADED_LYRIC);
                values.put(AudioTraceInfo.COLUMN_IS_UPLOADED_SONG,
                        AudioTraceInfo.VALUE_OF_NOT_UPLOADED_SONG);
                File file = new File(path);
                String md5 = MD5_NULL;
                if (file.exists() && file.isFile()) {
                    md5 = MD5Utils.getMD5(file);
                }
                values.put(AudioTraceInfo.COLUMN_MD5, md5);

                db.insert(AudioTraceInfo.NAME, "0", values);
            }
            db.setTransactionSuccessful();
        } finally {
            db.endTransaction();
        }
        db.close();
    }

    private final long INTERVAL_OF_DELAY_SEND_MESSAGE = 20 * 1000;

    private synchronized void notifyAllUploads() {
        if (mDBController.isExistNeedUploadSongs()) {
            mSyncHandler.removeMessages(MSG_CHECK_UPLOAD_SONGS);
            mSyncHandler.sendEmptyMessageDelayed(MSG_CHECK_UPLOAD_SONGS,
                    INTERVAL_OF_DELAY_SEND_MESSAGE);
        }

        SharedPreferences prefs = getSharedPreferences("Music",
                Context.MODE_WORLD_READABLE | Context.MODE_WORLD_WRITEABLE);
        Editor editor = prefs.edit();
        boolean needUploadLyrics = prefs.getBoolean(MusicConf.UploadKeys.KEY_NEED_UPLOAD_LYRICS,
                false);
        if (needUploadLyrics) {
            mSyncHandler.removeMessages(MSG_CHECK_UPLOAD_LYRICS);
            mSyncHandler.sendEmptyMessageDelayed(MSG_CHECK_UPLOAD_LYRICS,
                    INTERVAL_OF_DELAY_SEND_MESSAGE);
        } else if (checkTimeTrigger(MusicConf.UploadKeys.KEY_LAST_UPLOAD_LYRICS_TIME)) {
            mDBController.updateAllLyricsFlag(AudioTraceInfo.VALUE_OF_NOT_UPLOADED_LYRIC);
            editor.putBoolean(MusicConf.UploadKeys.KEY_NEED_UPLOAD_LYRICS, true);
            editor.commit();
            mSyncHandler.removeMessages(MSG_CHECK_UPLOAD_LYRICS);
            mSyncHandler.sendEmptyMessageDelayed(MSG_CHECK_UPLOAD_LYRICS,
                    INTERVAL_OF_DELAY_SEND_MESSAGE);
        }

        boolean needUploadStatistics = prefs.getBoolean(
                MusicConf.UploadKeys.KEY_NEED_UPLOAD_STATISTICS, false);
        if (needUploadStatistics
                || checkTimeTrigger(MusicConf.UploadKeys.KEY_LAST_UPLOAD_STATISTICS_TIME)) {
            mSyncHandler.removeMessages(MSG_CHECK_UPLOAD_STATISTICS);
            mSyncHandler.sendEmptyMessageDelayed(MSG_CHECK_UPLOAD_STATISTICS,
                    INTERVAL_OF_DELAY_SEND_MESSAGE);
        }
    }


    private final long THRESHOLD_OF_DATE_TRIGGER = 7 * 24 * 60 * 60 * 1000;

    private boolean checkTimeTrigger(String keyOfLastTime) {
        SharedPreferences prefs = getSharedPreferences("Music",
                Context.MODE_WORLD_READABLE | Context.MODE_WORLD_WRITEABLE);
        long lastTime = prefs.getLong(keyOfLastTime, 0);
        if (System.currentTimeMillis() - lastTime > THRESHOLD_OF_DATE_TRIGGER) {
            return true;
        }
        return false;
    }

    private class MediaObserver extends ContentObserver {
        private Handler handler;

        public MediaObserver(Handler handler) {
            super(handler);
            this.handler = handler;
        }

        @Override
        public void onChange(boolean selfChange) {
            if (MusicUtils.isSdcardUsable()) {
                // 1. When media scanner is scaning directory, this method will
                // be called many times frequently, so we delay some seconds to
                // avoid it.
                // 2. When sdcard is unmounting, the return value is still true
                // through "Environment.getExternalStorageState()", but we can't
                // get any data from media scanner DB, so we have to delay 20s
                // to avoid this situation. In most cases, unmounting will last
                // 4-5 seconds at most.
                handler.removeMessages(MSG_SYNC_AUDIO_TRACE_TABLE);
                handler.sendEmptyMessageDelayed(MSG_SYNC_AUDIO_TRACE_TABLE,
                        INTERVAL_OF_DELAY_SEND_MESSAGE);
            }
        }
    }

    private final long MINI_UPLOAD_TIME_INTERVAL = 60 * 60 * 1000;

    private BroadcastReceiver mNetworkStateReceiver = new BroadcastReceiver() {

        @Override
        public void onReceive(Context context, Intent intent) {
            if (intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
                NetworkInfo info = intent
                        .getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
                if (info.isConnected()) {
                    // upload 1 time in 1 hour at most.
                    if (System.currentTimeMillis() - mLastUploadTime > MINI_UPLOAD_TIME_INTERVAL) {
                        mLastUploadTime = System.currentTimeMillis();
                        notifyAllUploads();
                    }
                }
            }
        }
    };
}
